#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/syscall.h>
#include <mthread.h>
#include <os.h>

#define HDR_OFFSET 54
#define SHMP_KEY 0x42
#define MAGIC 0xbeefbeefbeefbeeflu
#define FIFO_BUF_MAX 2560

#define MAX_RECV_CNT 32
EthernetFrame recv_buffer[MAX_RECV_CNT]={6};
long recv_length[MAX_RECV_CNT] = {6};

const char response[] = "Response: ";

/* this should not exceed a page */
typedef struct echo_shm_vars {
    atomic_long magic_number;
    atomic_long available;
    char fifo_buffer[FIFO_BUF_MAX];
} echo_shm_vars_t;

void shm_read(char* shmbuf, atomic_long *_available, char* buf, size_t size)
{
    while (size > 0) {
        while(atomic_load_d(_available) != 1);
        //printf("                                         shm_read\n");
        int sz = size > FIFO_BUF_MAX ? FIFO_BUF_MAX : size;
        memcpy(buf, shmbuf, sz);
        size -= sz;

        atomic_exchange_d(_available, 0);
    }
}

void shm_write(char* shmbuf, atomic_long *_available, char* buf, size_t size)
{
    while (size > 0) {
        while(atomic_load_d(_available) != 0);
        //printf("                                          shm_write\n");

        int sz = size > FIFO_BUF_MAX ? FIFO_BUF_MAX : size;
        memcpy(shmbuf, buf, sz);
        size -= sz;

        atomic_exchange_d(_available, 1);
    }
}

int is_first(echo_shm_vars_t *vars)
{
    unsigned long my = atomic_exchange_d(&vars->magic_number, MAGIC);
    return my != MAGIC;
}

int main(int argc, char *argv[])
{
    int mode = 0;
    int size = 1;
    if(argc > 1) {
        size = atol(argv[1]);
    }
    if(argc > 2) {        
        if (strcmp(argv[2], "1") == 0) {
            mode = 1;
        }
    }
    
    echo_shm_vars_t *vars = (echo_shm_vars_t*) shmpageget(SHMP_KEY);
    if (vars == NULL) {
        sys_move_cursor(1, 1);
        printf("shmpageget failed!\n");
        return -1;
    }
    
    if (is_first(vars)) {
        sys_move_cursor(1, 1);
        printf("[ECHO RECV SIDE]\n");
        sys_net_irq_mode(mode);

        atomic_exchange_d(&vars->available, 0);
        sys_exec(argv[0], argc, argv, AUTO_CLEANUP_ON_EXIT);
        sys_move_cursor(1, 1);
        printf("[ECHO TASK] start recv(%d):                    \n", size);
        int recv_num = 0;
        while(size>0){
            if(size >= 10)
                recv_num = 10;
            else
                recv_num = size;
            sys_net_recv(recv_buffer, recv_num * sizeof(EthernetFrame), recv_num, recv_length);
            size -=recv_num;
            shm_write(vars->fifo_buffer, &vars->available, recv_buffer, recv_num * sizeof(EthernetFrame));
            shm_write(vars->fifo_buffer, &vars->available, recv_length, recv_num * sizeof(long));
        }
        
    } else {
        int print_location = 10;
        sys_move_cursor(1, print_location);
        printf("[ECHO SEND SIDE]\n");
        int send_num = 0;
        int packets = 0;
    while(size > 0){
        if(size >= 10)
                send_num = 10;
            else
                send_num = size;

        shm_read(vars->fifo_buffer, &vars->available, recv_buffer, send_num * sizeof(EthernetFrame));
        shm_read(vars->fifo_buffer, &vars->available, recv_length, send_num * sizeof(long));
        
        int i;
        int resp_len = strlen(response);

        for (int i = 0; i < send_num; ++i)
        {
            char *cur = recv_buffer[i];
            sys_move_cursor(1, print_location);
            printf("No.%d packet, recv_length[i] = %ld ...\n",packets , 71);
            memcpy(cur + HDR_OFFSET, response, resp_len);
            sys_net_send(recv_buffer[i], (packets >= 10)?71:70);
            sys_move_cursor(1, print_location + 1);
            printf("[ECHO TASK] Echo no.%d packets ...\n", packets+1);
            packets++;
            //cur += recv_length[i];
        }
        size -= send_num;
    }
        
        
    }

    shmpagedt((void*)vars);

    return 0;
}
